package com.humanminoritygame.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class HibernateUserRoundResultRepo extends HibernateGenericRepo implements UserRoundResultRepo{

  @Autowired
  public HibernateUserRoundResultRepo(SessionFactory sessionFactory) {
    setSessionFactory(sessionFactory);
  }
  
  @Override
  public void addUserRoundResult(UserRoundResult userRoundResult) {
    Session session = getSession();
    session.save(userRoundResult);
    session.flush();
  }

  @SuppressWarnings("unchecked")
  @Override
  public Set<UserRoundResult> getAllUserRoundResults() {
    Set<UserRoundResult> urrs =  new HashSet<UserRoundResult>();
    urrs.addAll(getSession().createQuery("SELECT urr FROM UserRoundResult urr").list());
    return urrs;
  }

  @SuppressWarnings("unchecked")
  @Override
  public UserRoundResult getLastUserRoundResult(Subscription subscription) {
    // TODO REVISAR
    Set<UserRoundResult> urrs =  new HashSet<UserRoundResult>();
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr WHERE urr.subscription=:sid");
    q.setEntity("sid",subscription);
    urrs.addAll(q.list());
    if(urrs.isEmpty()){
      return null;
    }
    return urrs.iterator().next();
  }

  @SuppressWarnings("unchecked")
  @Override
  public UserRoundResult getLastUserRoundResult(Round round) {
 // TODO REVISAR
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr WHERE urr.round=:rid ORDER BY urr.time DESC");
    q.setEntity("rid",round);
    List<UserRoundResult> urrs = q.list();
    if(urrs.isEmpty()){
      return null;
    }
    return (UserRoundResult) q.iterate().next(); 
  }

  @SuppressWarnings("unchecked")
  @Override
  public int getRanking(Subscription subscription) {
    Query q = getSession().createQuery("SELECT s FROM Subscription s " +
    		"WHERE s.room=:rid ORDER BY s.score DESC ");
    q.setEntity("rid", subscription.getRoom());
    List<Subscription> subscriptions = (List<Subscription>)q.list();
    int index = subscriptions.indexOf(subscription);
    if(index == -1){
      throw new RuntimeException("Cant get ranking of given subscription.");
    }
    return 100-showNiceRanking(index*100/subscription.getRoom().getQtyActiveSubscriptions());
  }

  @SuppressWarnings("unchecked")
  @Override
  public UserRoundResult getUserRoundResult(Round round,
      Subscription subscription) {
    getSession().flush();
    
    Query q = getSession().createQuery("SELECT u FROM UserRoundResult u WHERE u.round=:rid AND u.subscription=:sid");
    q.setEntity("rid",round);
    q.setEntity("sid",subscription);
    List<UserRoundResult> userRoundResults = (List<UserRoundResult>)q.list();
    if(userRoundResults.isEmpty()){
      return null;
    }
    if(userRoundResults.size()>1){
      throw new RuntimeException("Database error: There is more than one userRoundResult");
    }
    return userRoundResults.iterator().next();
  }

  @SuppressWarnings("unchecked")
  @Override
  public Set<UserRoundResult> getUserRoundResults(Round round) {
    Set<UserRoundResult> urrs = new HashSet<UserRoundResult>();
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr WHERE urr.round=:rid");
    q.setEntity("rid",round);
    urrs.addAll(q.list());
    return urrs;
  }

  @SuppressWarnings("unchecked")
  @Override
  public Set<UserRoundResult> getUserRoundResults(Subscription subscription) {
    Set<UserRoundResult> urrs = new TreeSet<UserRoundResult>();
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr WHERE urr.subscription=:sid ORDER BY urr.round.startDate");
    q.setEntity("sid",subscription);
    urrs.addAll(q.list());
    return urrs;
  }

  /**
   * este metodo se hizo porque el treeset no respeta el orden inverso
   */
  @SuppressWarnings("unchecked")
  @Override
  public List<UserRoundResult> getOrderedUserRoundResults(Subscription subscription) {
    List<UserRoundResult> urrs = new ArrayList<UserRoundResult>();
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr WHERE urr.subscription=:sid ORDER BY urr.round.startDate");
    q.setEntity("sid",subscription);
    urrs.addAll(q.list());
    return urrs;
  }

  
  
  @Override
  public ResultSummary processResults(Round round) {
    ResultSummary resp = calculateResult(round);
    setSubscriptionsRoundResults(round, resp);
    round.setSummary(resp);
    return resp;
  }

  private ResultSummary calculateResult(Round round) {
    int As = 0, Bs = 0;
    for (UserRoundResult userRoundResult : getUserRoundResults(round)) {
      if (userRoundResult.getAnswer().equals(AnswerType.A)) {
        As++;
      } else if(userRoundResult.getAnswer().equals(AnswerType.B)){
        Bs++;
      }
    }
    if (As == Bs) {
      try {
        UserRoundResult last = getLastUserRoundResult(round);
        if (last.getAnswer().equals(AnswerType.A)) {
          As--;
        } else {
          Bs--;
        }
      } catch (Exception e) {
        throw new RuntimeException("As== Bs y last de la ronda es null A="+As+" Bs: "+Bs);
      }
    }
    AnswerType answer;
    if (As > Bs) {
      answer = AnswerType.B;
    } else {
      answer = AnswerType.A;
    }
    //TODO: +rand
    int result = 100 * Math.abs(As - Bs) / (As + Bs);
    int acum = (As>Bs? 1 : -1) + round.getRoom().getAcumulatedValue();
    round.getRoom().setAcumulatedValue(acum);
    return new ResultSummary(answer, result , acum);
  }
  
  private void setSubscriptionsRoundResults(Round round, ResultSummary result) {
//    System.out.println("ROUND: " + round.getId());
    for (Subscription subscription : round.getRoom().getSubscriptions()) {
//      System.out.println("subscription: " + subscription.getUser().getName());
      UserRoundResult last = getUserRoundResult(round,subscription);
      //Si no es null, voto, entonces le seteo el puntaje
      if (last != null) {
        if (last.getAnswer().equals(result.getAnswer())) {
//          System.out.println("     voto: gano");
          last.addScore(result.getDifference());
        } else {
//          System.out.println("     voto: perdio");
          last.addScore((-1) * result.getDifference());
        }
        getSession().saveOrUpdate(last);
      }else{ //no voto => punish
        AnswerType answer;
        int score = 0;
        //si esta en la sala y no vota
        if(subscription.isActive()){
//          System.out.println("     NO voto: activo");
          answer = AnswerType.NOANSWER;
          score = (-1) * result.getDifference();
        } else {
          //salio de la sala
//          System.out.println("     NO voto: quit");
          answer = AnswerType.QUIT;
        }
        UserRoundResult punishment = new UserRoundResult(answer, subscription, round);
        punishment.addScore(score);
        addUserRoundResult(punishment);
      }
    }
  }

  /**
   * dice en que porcentaje de los mejores esta.
   * por ejemplo, si viene 26° y son 100, va a decir que esta dentro del mejor 30%
   * @param realRanking
   * @return
   */
  private int showNiceRanking(int realRanking){
    return (realRanking/10)*10;
  }

  @Override
  public boolean hasVoted(Subscription subscription, Round round) {
    Query q = getSession().createQuery("SELECT urr FROM UserRoundResult urr " +
      " WHERE urr.subscription=:subs AND urr.round=:round");
    q.setEntity("subs",subscription);
    q.setEntity("round",round);
    return (UserRoundResult) q.uniqueResult() != null;
  }

  
}
